﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using uTILLIty.Outlook.FlexFileAddIn.Properties;
using OL = Microsoft.Office.Interop.Outlook;

namespace uTILLIty.Outlook.FlexFileAddIn
{
	public class FilterableFolderListController : IComponent
	{
		private readonly int _controlGuid;
		private readonly IntPtr _controlHwnd;
		private readonly SynchronizationContext _synchronizationContext;

		private bool _disposed;

		//private void OnLoadStatusChanged(object sender, EventArgs<string> e)
		//{
		//	Target.SetStatusText(e.Value);
		//}

		private long _lastFilterListCall;


		private List<OL.ItemEvents_10_Event> _lastSelection;
		private FolderPopulationBackgroundWorker _worker;

		public FilterableFolderListController(IFilterableFolderList target, MapiFolderManager mapiFolderManager,
			OL.Application application)
		{
			_synchronizationContext = SynchronizationContext.Current;
			Application = application;
			MapiFolderManager = mapiFolderManager;
			Target = target;
			Target.OpenFolder += (o, e) => OnOpenSelectedFolder(target.SelectedItem);
			Target.MoveSelectedItems += (o, e) => OnMoveSelectedItems(target.SelectedItem);
			//SetList(MapiFolderManager.FolderList);
			Target.FilterTextChanged += (o, e) => FilterList(true);
			//_worker.RunWorkerCompleted += (o, e) => SetList(e.Result as IEnumerable<FolderInfo>);

			_controlGuid = Target.GetHashCode();
			_controlHwnd = Target.ContainingForm.Handle;

			RegisterKeys();
			MapiFolderManager.FolderList.ListChanged += OnFullListChanged;
			//MapiFolderManager.LoadStatusChanged += OnLoadStatusChanged;
			Application.ActiveExplorer().SelectionChange += OnExplorerSelectionChanged;
			Application.ActiveExplorer().BeforeItemCopy += OnBeforeItemCopy;
		}

		public IFilterableFolderList Target { get; set; }
		public MapiFolderManager MapiFolderManager { get; set; }
		public OL.Application Application { get; set; }

		public void Dispose()
		{
			if (_disposed)
				return;
			_disposed = true;


			UnRegisterKeys();

			MapiFolderManager.FolderList.ListChanged -= OnFullListChanged;
			//MapiFolderManager.LoadStatusChanged -= OnLoadStatusChanged;

			if (Application.ActiveExplorer() != null)
			{
				Application.ActiveExplorer().SelectionChange -= OnExplorerSelectionChanged;
				Application.ActiveExplorer().BeforeItemCopy -= OnBeforeItemCopy;
			}
			RaiseDisposed();
		}

		private void RegisterKeys()
		{
			const Keys keys = Keys.F12 | Keys.Control;
			WindowsHelper.RegisterHotKey(_controlHwnd, keys, _controlGuid);
		}

		private void UnRegisterKeys()
		{
			try
			{
				WindowsHelper.UnRegisterHotKey(_controlHwnd, _controlGuid);
			}
			catch (ObjectDisposedException)
			{
			}
		}

		private void OnBeforeItemCopy(ref bool cancel)
		{
			var result = MessageBox.Show(Resources.InterceptCopyMessage, Resources.InterceptCopyTitle,
				MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
			switch (result)
			{
				case DialogResult.Cancel:
					cancel = true;
					break;
				case DialogResult.No:
					cancel = false;
					break;
				case DialogResult.Yes:
					Target.FilterText = "";
					Target.SetFilterTextboxFocus();
					cancel = true;
					break;
			}
		}

		private void FilterList(bool force)
		{
			var selectedItems = GetSelection();
			if (_lastSelection != null && !selectedItems.Except(_lastSelection).Union(_lastSelection.Except(selectedItems)).Any())
			{
				if (!force)
					return; //same selection already filtered
			}
			_lastSelection = selectedItems;
			Target.SetStatusText(Resources.ItemsSelectedStatus, selectedItems.Count);

			if (MapiFolderManager.FolderList == null)
			{
#if DEBUG
				Debug.WriteLine("FilterableFolderListController - ignoring FilterList as MapiFolderManager.FolderList == null");
#endif
				return;
			}
			if (MapiFolderManager.FullLoadInProgress)
			{
#if DEBUG
				Debug.WriteLine(
					"FilterableFolderListController - ignoring FilterList as MapiFolderManager.FullLoadInProgress == true");
#endif
				return;
			}

			if (!force && _lastFilterListCall + 500 > DateTime.Now.Ticks)
				return;
			_lastFilterListCall = DateTime.Now.Ticks;

			if (_worker != null && _worker.IsBusy)
			{
#if DEBUG
				Debug.WriteLine("FilterableFolderListController - cancelling worker!");
#endif
				Task.Factory.StartNew(() =>
				{
					if (!_worker.CancellationPending)
						_worker.CancelAsync();
				});
				try
				{
					Thread.Sleep(150);
					_worker.Dispose();
				}
				catch (Exception ex)
				{
					Debug.WriteLine($"Error stopping worker. {ex.Message}");
				}
			}

			Debug.WriteLine($"Filtering list for {selectedItems.Count} selected items and text '{Target.FilterText}'...");
			_worker = new FolderPopulationBackgroundWorker();
			var args = new FolderPopulationBackgroundWorker.FolderPopulationArgs(
				Application, MapiFolderManager, _synchronizationContext, Target.FilterText, selectedItems);
			if (!string.IsNullOrEmpty(Target.FilterText))
				Target.ResetSelectedRow();
			Target.BindingSource.SuspendBinding();
			_worker.RunWorkerAsync(args);
			_worker.RunWorkerCompleted += (o, e) =>
			{
				if (!e.Cancelled)
				{
					Target.Invoke((MethodInvoker) delegate
					{
						SetList(args.TargetList);
						Target.BindingSource.ResumeBinding();
						Debug.WriteLine($"Found {args.TargetList.Count} items for selection and text '{Target.FilterText}'");
					});
				}
			};
		}

		private void SetList(IEnumerable<CandidateFolderInfo> list)
		{
			Target.BindingSource.DataSource = list; // == null
			if (Target.BindingSource.SupportsAdvancedSorting)
				Target.BindingSource.Sort = "DisplayPriority ASC, SortingName ASC";
			else if (Target.BindingSource.SupportsSorting)
				Target.BindingSource.Sort = "SortingName";
			//? null
			//: list.OrderBy(l => l.DisplayPriority).ThenBy(l => l.Name);
		}

		private void OnFullListChanged(object sender, ListChangedEventArgs e)
		{
#if DEBUG
			Debug.WriteLine("FilterableFolderListController.OnFullListChanged");
#endif
			FilterList(false);
		}

		private void OnExplorerSelectionChanged()
		{
			//fired multiple times, ie when reading-pane is also open
#if DEBUG
			Debug.WriteLine("FilterableFolderListController.OnExplorerSelectionChanged START");
#endif
			FilterList(false);
#if DEBUG
			Debug.WriteLine("FilterableFolderListController.OnExplorerSelectionChanged END");
#endif
		}

		private void OnOpenSelectedFolder(CandidateFolderInfo folderInfo)
		{
			var mapiFolder = MapiFolderManager.GetMapiFolder(folderInfo.FolderInfo);
			if (mapiFolder == null)
			{
				MessageBox.Show(string.Format(Resources.FolderNotFoundMessage, folderInfo),
					Resources.FolderNotFoundTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				MapiFolderManager.BeginLoadingFolders();
				return;
			}
			Application.ActiveExplorer().CurrentFolder = mapiFolder;
			// ReSharper disable RedundantCast
			((OL._Explorer) Application.ActiveExplorer()).Activate();
			// ReSharper restore RedundantCast
		}

		private void OnMoveSelectedItems(CandidateFolderInfo folderInfo)
		{
			var selection = GetSelection();
			var title = string.Format(Resources.MovingItemsTitle, selection.Count, folderInfo.Name);

			if (selection.Count == 0)
			{
				MessageBox.Show(Resources.NoItemsSelected, title, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				return;
			}

			var mapiFolder = MapiFolderManager.GetMapiFolder(folderInfo.FolderInfo);
			if (mapiFolder == null)
			{
				MessageBox.Show(string.Format(Resources.FolderNotFoundMessage, folderInfo),
					Resources.FolderNotFoundTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				MapiFolderManager.BeginLoadingFolders();
				return;
			}

			var confirmMsg = string.Format(Resources.MovingItemsMessage, selection.Count, folderInfo);
			var result = MessageBox.Show(confirmMsg, title, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
			switch (result)
			{
				case DialogResult.OK:
				case DialogResult.Yes:
					foreach (var olItem in selection)
					{
						try
						{
							dynamic item = olItem;
							item.Move(mapiFolder);
						}
						catch (Exception ex)
						{
							var msg = string.Format(Resources.ErrorMovingItem, ex.Message);
							MessageBox.Show(msg, title, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
						}
					}
					// ReSharper disable RedundantCast
					((OL._Explorer) Application.ActiveExplorer()).Activate();
					// ReSharper restore RedundantCast
					break;
				//case DialogResult.None:
				//case DialogResult.Cancel:
				//case DialogResult.Abort:
				//case DialogResult.Retry:
				//case DialogResult.Ignore:
				//case DialogResult.No:
				//default:
				//	break;
			}
		}

		private List<OL.ItemEvents_10_Event> GetSelection()
		{
			var list = new List<OL.ItemEvents_10_Event>();
			//var view = Application.ActiveExplorer().CurrentView as OL.TableView;
			//view.ViewType; //OL.OlViewType.olTableView
			//if (view != null && view.ShowFullConversations)

			var selection = Application.ActiveExplorer().Selection;
			var convHeaders = selection.GetSelection(OL.OlSelectionContents.olConversationHeaders);

			if (convHeaders.Count > 0) //headers selected
			{
				foreach (OL.ConversationHeader convHeader in convHeaders)
				{
					try
					{
						var items = convHeader.GetItems();
						list.AddRange(items.OfType<OL.ItemEvents_10_Event>());
					}
					catch (COMException)
					{
						/*
             * happened once during tests with Outlook 2013; outlook was not fully loaded yet, maybe
             * a timing problem. to avoid crashes, we ignore the exception
                HResult=-2147467259
                Message=Fehler beim Ausführen der Operation.
                Source=Microsoft Outlook
                ErrorCode=-2147467259
             * * */
					}
				}
			}
			else
			{
				list.AddRange(selection.OfType<OL.ItemEvents_10_Event>());
			}

			return list;
		}

		#region Implementation of IComponent

		public ISite Site { get; set; }
		public event EventHandler Disposed;

		private void RaiseDisposed()
		{
			if (Disposed != null)
				Disposed.Invoke(this, EventArgs.Empty);
		}

		#endregion
	}
}